function gewEmitter(gl,particle_shape)
{
  gewNode.call(this);
  this.localAabb = new gewAabb();
  this.localSphere = new gewSphere();
  this.gl = gl;
  this.shape = particle_shape;
  this.startTime = activeGame.time;
  this.particleNumber = 0;
  this.glBuffers = {};
  this.glBuffers.pos_life = gl.createBuffer();
  this.glBuffers.veloc_time = gl.createBuffer();
  this.glBuffers.accel_size = gl.createBuffer();
  this.glBuffers.color = gl.createBuffer();
  if (this.shape == GEW_PS_SPRITE2D) {
    this.glBuffers.texcoord = gl.createBuffer();
    this.glBuffers.indices  = gl.createBuffer();
  }
  
  // These array will be destroy after commit call!
  this.pos_lifeArray = [];
  this.veloc_timeArray = [];
  this.accel_sizeArray = [];
  this.colorArray = [];
  this.texcoordArray = [];
  this.indices =[];
  this.isReady = false;
}
extend2(gewEmitter, gewNode);

gewEmitter.prototype.writeParticleInfo = function(pos_life, veloc_time, accel_size, color){
  var max_par_num = GEW_PS_MAX_ELEMENT[this.shape];
  if(this.particleNumber>=max_par_num)
    return false;
  // Update aabb
  var start_pos = gewRegVec0;
  start_pos.set(pos_life.x,pos_life.y,pos_life.z);
  var end_pos = gewRegVec1;
  var v = gewRegVec2;
  v.set(veloc_time.x,veloc_time.y,veloc_time.z);
  var a  = gewRegVec3;
  a.set(accel_size.x,accel_size.y,accel_size.z);
  // end_pos = start_pos + v*life+ 1/2a*life^2
  var life = pos_life.w;
  v.mulScalar(life,v);
  a.mulScalar( 0.5*life*life,a);
  start_pos.add(v,end_pos).add(a,end_pos);  
  var aabb = this.localAabb
  switch(this.shape){
    case GEW_PS_POINT:
    case GEW_PS_STREAK:
      aabb.addInternalPoint(start_pos);
      aabb.addInternalPoint(end_pos);
      break;
    case GEW_PS_SPRITE2D:
      var size = accel_size.w;
      aabb.addInternalPointXYZ(start_pos.x-size,start_pos.y-size,start_pos.z-size);
      aabb.addInternalPointXYZ(start_pos.x+size,start_pos.y+size,start_pos.z+size);
      aabb.addInternalPointXYZ(end_pos.x-size,end_pos.y-size,end_pos.z-size);
      aabb.addInternalPointXYZ(end_pos.x+size,end_pos.y+size,end_pos.z+size);
      break;   
  }
  // write particle info 
  var pos_lifes = this.pos_lifeArray;
  var veloc_times = this.veloc_timeArray;
  var accel_sizes = this.accel_sizeArray;
  var colors = this.colorArray;
  var texcoords = this.texcoordArray;
  var indices = this.indices;
  switch (this.shape) 
  {
    case GEW_PS_POINT:
      pos_lifes.push(pos_life.x, pos_life.y, pos_life.z, pos_life.w);
      veloc_times.push(veloc_time.x, veloc_time.y, veloc_time.z, veloc_time.w);
      accel_sizes.push(accel_size.x, accel_size.y, accel_size.z, accel_size.w);
      colors.push(color.x, color.y, color.z, color.w);
      break;
    case GEW_PS_STREAK:
      pos_lifes.push(pos_life.x, pos_life.y, pos_life.z, pos_life.w, 
                     pos_life.x, pos_life.y, pos_life.z, pos_life.w);
      veloc_times.push(veloc_time.x, veloc_time.y, veloc_time.z, veloc_time.w, 
                       veloc_time.x, veloc_time.y, veloc_time.z, veloc_time.w);
      accel_sizes.push(accel_size.x, accel_size.y, accel_size.z, 0.0, 
                       accel_size.x, accel_size.y, accel_size.z, accel_size.w);
      colors.push(color.x, color.y, color.z, color.w, 
                  color.x, color.y, color.z, 0.0);
      break;
    case GEW_PS_SPRITE2D:
      pos_lifes.push(pos_life.x, pos_life.y, pos_life.z, pos_life.w, 
                     pos_life.x, pos_life.y, pos_life.z, pos_life.w, 
                     pos_life.x, pos_life.y, pos_life.z, pos_life.w, 
                     pos_life.x, pos_life.y, pos_life.z, pos_life.w);
      
      veloc_times.push(veloc_time.x, veloc_time.y, veloc_time.z, veloc_time.w, 
                       veloc_time.x, veloc_time.y, veloc_time.z, veloc_time.w, 
                       veloc_time.x, veloc_time.y, veloc_time.z, veloc_time.w, 
                       veloc_time.x, veloc_time.y, veloc_time.z, veloc_time.w);
      
      accel_sizes.push(accel_size.x, accel_size.y, accel_size.z, accel_size.w, 
                      accel_size.x, accel_size.y, accel_size.z, accel_size.w, 
                      accel_size.x, accel_size.y, accel_size.z, accel_size.w, 
                      accel_size.x, accel_size.y, accel_size.z, accel_size.w);
      
      colors.push(color.x, color.y, color.z, color.w, 
                  color.x, color.y, color.z, color.w, 
                  color.x, color.y, color.z, color.w, 
                  color.x, color.y, color.z, color.w);
      
      texcoords.push(-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0);
      var offset = this.particleNumber* 4;
      indices.push(offset, offset + 1, offset + 2, offset, offset + 2, offset + 3);
      break;
  }
  this.particleNumber++;
  return true;
}

/////////////////////////////////////////////////////////////
// Add particles function
gewEmitter.prototype.addParticleByPoints = function(particle_number, position_array, generateParticleFunction, genFuncParam){
  var emit_pos_num = position_array.length;  
  for (var i = 0; i < particle_number; i++) {
    // Generate particle
    var emit_id = i % emit_pos_num;
    var emit_pos = position_array[emit_id];
    var in_out_pos_life = new gewVec4();
    in_out_pos_life.set(emit_pos.x, emit_pos.y, emit_pos.z, 1.0);
    var out_veloc_time = new gewVec4();
    var out_accel_size = new gewVec4();
    var out_color = new gewVec4();
    if (generateParticleFunction) 
      generateParticleFunction(in_out_pos_life, out_veloc_time, out_accel_size, out_color, genFuncParam);
    else 
      this.defaultGenParticleFunc(in_out_pos_life, out_veloc_time, out_accel_size, out_color);
    // Write particle's info 
    var is_not_full = this.writeParticleInfo(in_out_pos_life, out_veloc_time, out_accel_size, out_color);
    if(!is_not_full)
      return false;
  }
  return true;
}

gewEmitter.prototype.addParticleByGrid = function(particle_number,
                                                  start_pos, 
                                                  grid_w,grid_h,
                                                  cell_w,cell_h, 
                                                  generateParticleFunction, genFuncParam){
  var col_num = Math.ceil(grid_w/cell_w);
  var row_num = Math.ceil(grid_h/cell_h);
  var cell_num = col_num*row_num;
  for (var i = 0; i < particle_number; i++) {
    // Generate particle
    var emit_id = i % cell_num;
    var emit_row = Math.floor(emit_id/col_num);
    var emit_col = emit_id%col_num;
    var in_out_pos_life = new gewVec4();
    var rand1 = Math.random()-0.5;
    var rand2 = Math.random()-0.5;
    in_out_pos_life.x = start_pos.x + (emit_col+rand1)*cell_w;
    in_out_pos_life.y = start_pos.y;
    in_out_pos_life.z = start_pos.z + (emit_row+rand2)*cell_h;
    var out_veloc_time = new gewVec4();
    var out_accel_size = new gewVec4();
    var out_color = new gewVec4();
    if (generateParticleFunction) 
      generateParticleFunction(in_out_pos_life, out_veloc_time, out_accel_size, out_color, genFuncParam);
    else 
      this.defaultGenParticleFunc(in_out_pos_life, out_veloc_time, out_accel_size, out_color);
    // Write particle's info 
    var is_not_full = this.writeParticleInfo(in_out_pos_life, out_veloc_time, out_accel_size, out_color);
    if(!is_not_full)
      return false;
  }
  return true;
}


gewEmitter.prototype.commit = function()
{
  var aabb = this.localAabb;
  aabb.init();
  // Update bounding sphere based on aabb
  this.localSphere.center = aabb.min.add(aabb.max).divScalar(2);
  this.localSphere.radius = this.localSphere.center.sub(aabb.min).getLength();
  // Create webglBuffer
  var w_pos_lifes = new WebGLFloatArray(this.pos_lifeArray);
  var w_veloc_times = new WebGLFloatArray(this.veloc_timeArray);
  var w_accel_sizes = new WebGLFloatArray(this.accel_sizeArray);
  var w_colors = new WebGLFloatArray(this.colorArray);
  var w_texcoords = new WebGLFloatArray(this.texcoordArray);
  var w_indices = new WebGLUnsignedShortArray(this.indices);
  switch(this.shape)
  {
    case GEW_PS_POINT:
    case GEW_PS_STREAK:
      this.createGLBuffers(w_pos_lifes,w_veloc_times,w_accel_sizes,w_colors);
      break;
    case GEW_PS_SPRITE2D:
      this.createGLBuffers(w_pos_lifes,w_veloc_times,w_accel_sizes,w_colors,w_texcoords,w_indices);
      break;
  }
  this.isReady = true;
  
  delete this.pos_lifeArray;
  delete this.veloc_timeArray;
  delete this.accel_sizeArray;
  delete this.colorArray;
  delete this.texcoordArray;
  delete this.indices;
}

gewEmitter.prototype.createGLBuffers = function(pos_lifes,
                                                veloc_times,
                                                accel_sizes,
                                                colors,
                                                texcoords,
                                                indices)
{
  var gl = this.gl;
  var glBuffers = this.glBuffers;
  gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.pos_life);
  gl.bufferData(gl.ARRAY_BUFFER, pos_lifes, gl.STATIC_DRAW);
  
  gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.veloc_time);
  gl.bufferData(gl.ARRAY_BUFFER, veloc_times, gl.STATIC_DRAW);
  
  gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.accel_size);
  gl.bufferData(gl.ARRAY_BUFFER, accel_sizes, gl.STATIC_DRAW);
  
  gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.color);
  gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
  
  if(texcoords) {
    gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.texcoord);
    gl.bufferData(gl.ARRAY_BUFFER, texcoords, gl.STATIC_DRAW);
  }
  if(indices){  
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glBuffers.indices);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  }  
}

gewEmitter.prototype.render = function(material)
{
  var gl = this.gl;
  var time = activeGame.time;
  var effect = material.effect;
  var shader;
  if(!this.isReady)
    return;
  switch(material.type)
  {
    case GEW_PS_POINT:
      shader = effect.shaders.point;
      break;
    case GEW_PS_STREAK:
      shader = effect.shaders.streak;
      break;
    case GEW_PS_SPRITE2D:
      shader = effect.shaders.sprite2d;
      break;
  }
  gl.uniform1f(shader.local_time, this.startTime / 1000.0);
  gl.uniform1f(shader.time, time / 1000.0 + 0.5);
  
  var glBuffers = this.glBuffers;
  gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.pos_life);
  gl.vertexAttribPointer(shader.in_pos_life, 4, gl.FLOAT, false, 0, 0);
  gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.veloc_time);
  gl.vertexAttribPointer(shader.in_veloc_time, 4, gl.FLOAT, false, 0, 0);
  gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.accel_size);
  gl.vertexAttribPointer(shader.in_accel_size, 4, gl.FLOAT, false, 0, 0);
  gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.color);
  gl.vertexAttribPointer(shader.in_color, 4, gl.FLOAT, false, 0, 0);
  switch(material.type)
  {
    case GEW_PS_POINT:
      gl.drawArrays(gl.POINTS, 0, this.particleNumber);
      break;
    case GEW_PS_STREAK:
      gl.drawArrays(gl.LINES, 0, this.particleNumber*2);
      break;
    case GEW_PS_SPRITE2D:
      gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.texcoord);
      gl.vertexAttribPointer(shader.in_texcoord, 2, gl.FLOAT, false, 0, 0);
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glBuffers.indices);
      gl.drawElements(gl.TRIANGLES, this.particleNumber * 6, gl.UNSIGNED_SHORT, 0);
      break;
  }
}

gewEmitter.prototype.defaultGenParticleFunc = function(in_out_pos_life, out_veloc_time, out_accel_size,out_color,in_particle_param)
{
  // Life : random in range 2.0-4.0
  var rand_f = Math.random();
  var life = gewMix(2,4,rand_f);
  // Velocity : random in sphere S( (0,0,0), 2);
  var rand_f0 = Math.random()+0.01 -0.5; // avoid vec3(0,0,0) case
  var rand_f1 = Math.random() - 0.5;
  var rand_f2 = Math.random() - 0.5;
  var rand_vec = gewRegVec0;
  rand_vec.set(rand_f0,rand_f1,rand_f2);
  rand_vec.normalize();
  var velocity = gewRegVec1;
  rand_vec.mulScalar(2,velocity);
  // Time : random in range 0.0-3.0
  rand_f = Math.random();
  var time = gewMix(0.0,3.0,rand_f);
  
  // Fix accel
  var accel = new gewVec3(0,-1.5,0);
  // Size : random in range : 1-5;
  var rand_f = Math.random();
  var size = gewMix(1,5,rand_f);
  
  // Color : random in range : (0,0,0,0.8)->(1,1,1,0.8)
  rand_f0 = Math.random(); // avoid vec3(0,0,0) case
  rand_f1 = Math.random();
  rand_f2 = Math.random();
  var rand_f3 = 0.8;
  var color = new gewVec4(rand_f0,rand_f1,rand_f2,rand_f3);
  // Write ouput
  in_out_pos_life.w = life;
  out_veloc_time.set(velocity.x,velocity.y,velocity.z,time);
  out_accel_size.set(accel.x,accel.y,accel.z,size);  
  out_color.set(color.x,color.y,color.z,color.w);
}

/* in_particle_param = { minLength,maxLength, //streak length 
                         startVelocity,  
                         minLife,maxLife, 
                         minStartTime, maxStartTime, 
                         color,
                         gravity,
                         wind,
                         minStrength,maxStrength, // strength again wind
                         }                         
*/ 
gewEmitter.prototype.rainGenParticleFunc = function(in_out_pos_life, out_veloc_time, out_accel_size,out_color,in_particle_param)
{
  // Life 
  var rand_f = Math.random();
  var life = gewMix(in_particle_param.minLife,in_particle_param.maxLife,rand_f);
  // Fixed Velocity 
  var velocity = in_particle_param.startVelocity;
  // Time 
  rand_f = Math.random();
  var time = gewMix(in_particle_param.minStartTime,in_particle_param.maxStartTime,rand_f);
  
  // Accel = gravity + accel_by_wind
  rand_f = Math.random();
  var strength = gewMix(in_particle_param.minStrength,in_particle_param.maxStrength,rand_f);
  var accel_by_wind = in_particle_param.wind.divScalar(strength);
  var accel = in_particle_param.gravity.add(accel_by_wind);
  // Streak length
  var rand_f = Math.random();
  var size = gewMix(in_particle_param.minLength,in_particle_param.maxLength,rand_f);
  
  // Color
  var color = in_particle_param.color;
  // Write ouput
  in_out_pos_life.w = life;
  out_veloc_time.set(velocity.x,velocity.y,velocity.z,time);
  out_accel_size.set(accel.x,accel.y,accel.z,size);  
  out_color.set(color.x,color.y,color.z,color.w);
}
